﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data;
using RayDen.Library.Data.Imaging;
using RayDen.Library.Data.Import.OBJLoader;
using RayDen.Library.Entity;
using RayDen.Library.Entity.Frames;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Entity.Scene;
using RayDen.Library.Entity.SceneGraph;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Data;

namespace PathEngine
{
    public class DatasetProcessor
    {
        private IParameterBag config;

        public List<ILight> Lights;
        public List<MaterialInfo> MaterialInfos;
        public List<CameraInfo> Cameras;
        public List<TriangleMesh> Meshes;

        public InfiniteLight EnvironmentMap;
        public TextureAtlas Textures;
        public Dictionary<string, string[]> texturesMap;

        public RenderPrimitive[] Primitives
        {
            get
            {
                return Meshes.Select(mesh => new RenderPrimitive() { MaterialName = mesh.MaterialName, ShapeId = mesh.Id }).ToArray();
            }
        }

        protected List<INodeVisitor> visitors;

        public DatasetProcessor(IParameterBag cfg = null)
        {
            if (cfg != null)
            {
                this.config = cfg;
            }
            this.Lights = new List<ILight>();
            this.MaterialInfos = new List<MaterialInfo>();
            this.Cameras = new List<CameraInfo>();
            texturesMap = new Dictionary<string, string[]>();
            Textures = new TextureAtlas();
        }

        public void OpenFrame(FrameDescription frame)
        {
            if (this.config == null)
            {
                config = frame;
            }
            else
            {
                config.Merge(frame);
            }

            var svc = new SceneGraphService();
            svc.Populate(new GlobalIndexObjLoader(), new MaterialLoader());
            svc.OpenFrame(frame);
            Meshes = new List<TriangleMesh>();
            var visitors = new List<DependentComponentBase>
                {
                    CreateVisitor<MaterialsBuilder>(),
                    CreateVisitor<LightBuilder>(),
                    CreateVisitor<CameraBuilder>(),
                    CreateVisitor<GeoDataBuilder>()//MeshBuilder
                };
            visitors.ForEach(item => item.Populate(this.Lights, this.MaterialInfos, this.Cameras, Meshes));
            foreach (var nodeVisitor in visitors.Cast<INodeVisitor>())
            {
                svc.SceneGraph.Visit(nodeVisitor);
            }
            LoadTextures();
        }

        public FrameDataset CreateDataset()
        {
            return new FrameDataset()
                {
                    //MaterialProvider = new DefaultMaterialContainer(new )
                };
        }

        private void LoadTextures()
        {
            Console.WriteLine("Start loading textures");
            texturesMap = new Dictionary<string, string[]>();
            Textures = new TextureAtlas();

            foreach (var materialInfo in this.MaterialInfos)
            {
                var textures = new List<string>();

                if (materialInfo.Diffuse != null)
                {
                    var info = materialInfo.Diffuse as ImageTextureInfo;
                    if (info != null)
                    {
                        ProcessMaterialTexture(info, textures);
                    }
                }

                if (materialInfo.BumpMap != null)
                {
                    ProcessMaterialTexture((ImageTextureInfo)materialInfo.BumpMap, textures);
                }

                if (textures.Any())
                    texturesMap.Add(materialInfo.Name.ToLower(), textures.ToArray());
            }
            Console.WriteLine("Complete loading textures");
        }

        private void ProcessMaterialTexture(ImageTextureInfo diff, List<string> textures)
        {
            var texPath = this.FindTexture(diff);
            if (texPath != null)
            {
                this.Textures.AddTexture(new ImageTextureInfo()
                {
                    FilePath = texPath,
                    Name = Path.GetFileName(texPath)
                });
                textures.Add(Path.GetFileName(texPath));
            }
        }

        private string FindTexture(ImageTextureInfo ii)
        {
            string filePath = ii.FilePath;
            if (File.Exists(filePath))
                return filePath;
            if (File.Exists(ii.Name))
            {
                return Directory.GetCurrentDirectory() + @"\" + ii.Name;
            }
            return null;
        }

        private TVisitor CreateVisitor<TVisitor>(params object[] deps)
          where TVisitor : DependentComponentBase, INodeVisitor, new()
        {
            var vis = new TVisitor();
            vis.Populate(this, deps);
            return vis;
        }

    }


    #region Scene Graph Processing Components

    internal abstract class FrameDatasetBuilderComponentBase<TContainer, TElement> : DependentComponentBase, INodeVisitor
       where TElement : SceneGraphElement
    {
        internal TContainer Owner
        {
            get { return this.BaseContainer.Get<TContainer>(); }

        }

        internal SceneGraphInfo SceneGraph
        {
            get { return this.BaseContainer.Get<SceneGraphInfo>(); }
        }


        public void Setup(TContainer scn, SceneGraphInfo grp)
        {
            this.BaseContainer.Populate(scn, grp);
        }

        public void Visit(SceneGraphElement node)
        {
            if (node is TElement)
                this.VisitInternal((TElement)node);
        }

        protected abstract void VisitInternal(TElement node);
    }

    internal class MaterialsBuilder : FrameDatasetBuilderComponentBase<List<MaterialInfo>, SceneMaterialDataNode>
    {
        protected override void VisitInternal(SceneMaterialDataNode node)
        {
            var mi = (MaterialInfo)node.GetDataItem();
            this.Owner.Add(mi);
            //tex
        }

    }

    internal class GeoDataBuilder : FrameDatasetBuilderComponentBase<List<TriangleMesh>, GeometryGroupNode>
    {
        protected override void VisitInternal(GeometryGroupNode node)
        {
            var sceneData = (SceneGeometryInfo)SceneGraph.ResolveDataNode<SceneGeometryDataNode>(node).GetDataItem();
            foreach (var geometryInfo in sceneData.Geometry)
            {
                this.Owner.Add(new TriangleMesh(geometryInfo));
            }
        }
    }

    internal class LightBuilder : FrameDatasetBuilderComponentBase<DatasetProcessor, LightsourceNode>
    {

        protected override void VisitInternal(LightsourceNode node)
        {
            var li = (LightsourceInfo)node.GetDataItem();
            ILight light = null;
            switch (li.Type)
            {
                case LightSourceTypes.Area:
                    var meshLight = new MeshLight()
                    {
                        gain = new RgbSpectrum(li.Gain),
                        MeshName = li.MeshName,
                        spectra = SPD_Data.FromSampled(SPD_Data.A, SpectrumType.Illuminant),
                        LightName = li.MeshName
                    };
                    light = meshLight;
                    break;
                case LightSourceTypes.EnvironmentMap:
                    if (this.Owner.EnvironmentMap == null)
                    {
                        this.Owner.EnvironmentMap = new InfiniteLight(li.ImageName);
                        light = this.Owner.EnvironmentMap;
                    }
                    break;
                case LightSourceTypes.Point:
                    light = new PointLight()
                    {
                        Position = (Point)li.Position,
                        Power = new RgbSpectrum(li.Spectra),
                        Spectra = SPD_Data.FromSampled(SPD_Data.D5000, SpectrumType.Illuminant)
                    };
                    break;
            }
            if (light != null)
            {
                this.Owner.Lights.Add(light);
            }
        }
    }

    internal class CameraBuilder : FrameDatasetBuilderComponentBase<DatasetProcessor, CameraNode>
    {
        protected override void VisitInternal(CameraNode node)
        {
            this.Owner.Cameras.Add((CameraInfo)node.GetDataItem());
        }
    }
    #endregion
}
